Skip to content

Unify workspace tool surface tabs#38

Merged
AkaraChen merged 6 commits into
masterfrom
workspace-tool-surface-tabs
Jun 11, 2026
Merged

Unify workspace tool surface tabs#38
AkaraChen merged 6 commits into
masterfrom
workspace-tool-surface-tabs

Conversation

@AkaraChen

@AkaraChen AkaraChen commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • move workspace tools into one shared surface that can dock in the right sidebar, dialog, or standalone window
  • keep Files and Git changes as pinned tabs while file previews, git diffs, terminals, and browsers open as dynamic tabs
  • gate the tool surface on a real chat id and persist per-chat in-memory tab snapshots
  • use shared TypeScript payload types for workspace tool surface IPC instead of runtime shape guards

Verification

  • npm --prefix desktop run typecheck
  • npx prettier --check targeted desktop files
  • git diff --check
  • npx agent-browser Electron flow for draft gating, file/git dynamic tabs, terminal/browser tabs, dialog/window/dock migration, and window close docking

Summary by CodeRabbit

  • New Features
    • Workspace Tools: unified tool surface with file preview, Git diffs, terminal, and native browser; flexible hosts (sidebar/dialog/window).
    • Workspace Browser: embedded native browser view with navigation controls and events.
    • Terminal upgrades: multi-subscriber sessions, scrollback replay, explicit kill API.
    • File preview API: secure read with size/type handling and preview limits.
    • Preload & API: new renderer bridges, store, and IPC integrations for tool lifecycle and state.

AkaraChen added 5 commits June 8, 2026 08:39
Add terminal and browser new-tab controls to dialog/window tool workbenches.

Register window-host tool instances for cwd sync, keep git diff as a replaceable dialog document, and replay terminal scrollback without echoing xterm responses back into the PTY.
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2b8486b0-e9f8-4311-8e71-812cf9d36d11

📥 Commits

Reviewing files that changed from the base of the PR and between cf74c0b and a26224b.

📒 Files selected for processing (7)
  • desktop/src/main/features/workspace-browser/ipc.ts
  • desktop/src/main/features/workspace-tools/service.ts
  • desktop/src/renderer/app/router.tsx
  • desktop/src/renderer/app/workspace/workspace-browser-view.tsx
  • desktop/src/renderer/app/workspace/workspace-tool-host.tsx
  • desktop/src/renderer/app/workspace/workspace-tool-store.ts
  • desktop/src/shared/workspace-browser.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • desktop/src/shared/workspace-browser.ts
  • desktop/src/main/features/workspace-tools/service.ts
  • desktop/src/renderer/app/workspace/workspace-browser-view.tsx
  • desktop/src/renderer/app/workspace/workspace-tool-store.ts
  • desktop/src/main/features/workspace-browser/ipc.ts
  • desktop/src/renderer/app/workspace/workspace-tool-host.tsx

📝 Walkthrough

Walkthrough

This PR introduces a comprehensive workspace tools system enabling docked and windowed tool surfaces (terminal, browser, file preview, git diffs). It refactors terminal IPC to support multi-subscriber sessions with scrollback buffering, adds workspace browser view management via Electron WebContentsView, and integrates a unified tool UI into the workspace sidebar and window hosts.

Changes

Terminal Session Refactoring

Layer / File(s) Summary
Terminal shared types and channels
desktop/src/shared/terminal.ts
Adds TERMINAL_KILL_CHANNEL, optional sessionId in TerminalCreateInput, TerminalKillInput, "replay" event variant, and kill() methods on session controller and API.
Terminal IPC main-process refactoring
desktop/src/main/features/terminal/ipc.ts
Refactors from single-owner to multi-subscriber broadcast: session creation attaches subscribers and resizes PTY, PTY output appends to scrollback and broadcasts to all subscribers, dispose detaches only (no kill), new killTerminalSession() exported.
Terminal preload bridge session control
desktop/src/preload/bridges/terminal.ts
create() accepts optional input.sessionId, returned object gains kill(), top-level terminalApi.kill(input) added.

Workspace Browser View System

Layer / File(s) Summary
Workspace browser shared types and channels
desktop/src/shared/workspace-browser.ts
Defines IPC channels, event channel factory, input/state types, and WorkspaceBrowserApi interface.
Workspace browser IPC main-process implementation
desktop/src/main/features/workspace-browser/ipc.ts
Implements registerWorkspaceBrowserIpc() with lifecycle/navigation handlers, manages instances by id, configures webContents, broadcasts state to renderer windows.
Workspace browser preload bridge
desktop/src/preload/bridges/workspace-browser.ts
Exposes window.workspaceBrowser with methods forwarding to main, onEvent subscriptions with type-guard filtering.
Workspace browser UI components
desktop/src/renderer/app/workspace/workspace-browser-view.tsx
WorkspaceBrowserNativeView and WorkspaceBrowserToolView for rendering, state sync, URL navigation.

Workspace Tools Surface and Management System

Layer / File(s) Summary
Workspace tools type system
desktop/src/shared/workspace-tool-instances.ts, desktop/src/shared/workspace-tool-surface.ts, desktop/src/shared/workspace-tools.ts
Defines tool host/instance discriminated unions, surface context/snapshot types, and file read input/result types.
Workspace tools main-process service and IPC
desktop/src/main/features/workspace-tools/service.ts, desktop/src/main/features/workspace-tools/ipc.ts
Implements workspaceReadFile() with validation, workspaceToolsReadFile IPC procedure.
Workspace tool window main-process controller
desktop/src/main/windows/workspace-tool-window.ts
Manages surface state, host mode switching to window with BrowserWindow lifecycle, broadcasts state, prevents title spoofing.
Workspace tools preload bridges
desktop/src/preload/bridges/desktop-window.ts, desktop/src/preload/index.ts
Extends desktopWindow bridge with tool event handlers, surface queries, instance operations, context setters.
Workspace tools client-side state management
desktop/src/renderer/app/workspace/workspace-tool-store.ts
Zustand store tracking surface context/host/snapshots, actions to focus/update host/snapshots, default snapshot helpers.
Workspace tools surface UI component
desktop/src/renderer/app/workspace/workspace-tool-host.tsx
WorkspaceToolSurface routes pinned (files/git) and dynamic (browser/terminal/preview/diff) tabs, extensive utilities for patch parsing and highlighter preload.
Workspace terminal view renderer
desktop/src/renderer/app/workspace/workspace-terminal-view.tsx
Renders xterm.js terminal with theme, FitAddon sizing, session controller wiring, replay write suppression.
Right sidebar refactoring to use workspace tools
desktop/src/renderer/app/workspace/workspace-right-sidebar.tsx
Simplifies sidebar from tabbed multi-panel to single WorkspaceToolSurface with chatId prop.
Workspace page tool docking integration
desktop/src/renderer/app/workspace/workspace-page.tsx
Derives docked tool context, auto-opens sidebar for sidebar host, integrates context/dialog bridges.

Infrastructure and Global Wiring

Layer / File(s) Summary
IPC registration and desktop window channels
desktop/src/main/ipc/register.ts, desktop/src/shared/desktop-window.ts
Registers workspace tool/browser IPC, exports channel constants.
Global type declarations and API extensions
desktop/src/renderer/env.d.ts
Extends window.desktopWindow and adds window.workspaceBrowser API, extends ElectronWebviewElement.
API client and query key extensions
desktop/src/renderer/platform/api-client.ts, desktop/src/renderer/platform/query-keys.ts
Adds workspaceTools.readFile() and cache key generator.
Router and UI store updates
desktop/src/renderer/app/router.tsx, desktop/src/renderer/app/workspace/workspace-ui-store.ts
Adds /workspace-tools route, removes old right-sidebar tab state.

🐰 A rabbit's tools now dwell in many homes,
In sidebars docked, in windows, or in dialogs they roam.
Terminals echo with multi-voice praise,
While browsers browse through workspace's bright maze! 🌿✨

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective: unifying workspace tool surface tabs into a shared tabbed interface.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch workspace-tool-surface-tabs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cf74c0b009

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread desktop/src/main/features/workspace-tools/service.ts Outdated
Comment thread desktop/src/renderer/app/workspace/workspace-browser-view.tsx Outdated
Comment thread desktop/src/renderer/app/workspace/workspace-page.tsx
Comment thread desktop/src/main/features/workspace-browser/ipc.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
desktop/src/main/features/workspace-browser/ipc.ts (1)

264-275: Handle loadURL() rejections for diagnostics/recovery.

webContents.loadURL() rejects on navigation failures, but Electron already attaches a noop rejection handler, so this won’t typically create a main-process unhandled-rejection crash. Still, adding a .catch() is a low-effort way to ensure failures are observed/logged and to support any recovery behavior beyond the did-fail-load state updates.

🛡️ Proposed fix
-  void instance.view.webContents.loadURL(nextUrl);
+  void instance.view.webContents.loadURL(nextUrl).catch(() => {
+    // `did-fail-load` updates state; avoid an unhandled rejection in main.
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src/main/features/workspace-browser/ipc.ts` around lines 264 - 275,
The loadWorkspaceBrowserUrl function calls
instance.view.webContents.loadURL(...) without handling promise rejections;
update both places where loadURL is called in loadWorkspaceBrowserUrl to attach
a .catch() that logs the error (e.g., console.error or the app logger) and
optionally triggers any recovery/diagnostic behavior (such as emitting an
IPC/event or calling instance/WorkspaceBrowserInstance recovery methods) so
navigation failures are observed and can be acted on.
desktop/src/main/features/workspace-tools/service.ts (1)

115-115: ⚡ Quick win

The fallback to treePathInput appears unreachable.

resolveWorkspaceTreePath throws if absolutePath would be outside the workspace root. Since absolutePathToTreePath only returns null when the relative path escapes the workspace, this fallback on line 115 should never execute.

♻️ Proposed simplification
- const treePath = absolutePathToTreePath(root, absolutePath) ?? treePathInput;
+ const treePath = absolutePathToTreePath(root, absolutePath)!;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src/main/features/workspace-tools/service.ts` at line 115, The
fallback to treePathInput when computing treePath is unreachable because
absolutePathToTreePath only returns null for paths that escape the workspace and
resolveWorkspaceTreePath already throws for those cases; remove the unreachable
"?? treePathInput" fallback and assign treePath directly from
absolutePathToTreePath(root, absolutePath), or alternatively handle the
out-of-workspace case explicitly where resolveWorkspaceTreePath is called
(adjust the call sites of absolutePathToTreePath and resolveWorkspaceTreePath
rather than keeping the redundant fallback).
desktop/src/renderer/app/router.tsx (1)

22-25: 💤 Low value

Consider inlining the trivial wrapper component.

The WorkspaceToolWindowRoutePage wrapper (lines 43-45) adds no behavior and could be replaced by directly rendering <WorkspaceToolWindowPage /> inline, consistent with other simple routes in this file (e.g., line 16-18).

♻️ Proposed simplification
- <Route
-   component={WorkspaceToolWindowRoutePage}
-   path="/workspace-tools"
- />
+ <Route path="/workspace-tools">
+   <WorkspaceToolWindowPage />
+ </Route>

Remove the wrapper function:

-function WorkspaceToolWindowRoutePage() {
-  return <WorkspaceToolWindowPage />;
-}
-

Also applies to: 43-45

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@desktop/src/renderer/app/router.tsx` around lines 22 - 25, Remove the trivial
wrapper WorkspaceToolWindowRoutePage and inline WorkspaceToolWindowPage in the
Route so the route matches the simple-route pattern used elsewhere: replace the
Route using component={WorkspaceToolWindowRoutePage} with the same inline form
used in other routes (e.g., component={WorkspaceToolWindowPage} or
element={<WorkspaceToolWindowPage />} to match the file's routing API) and
delete the now-unused WorkspaceToolWindowRoutePage function/definition.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@desktop/src/main/features/workspace-browser/ipc.ts`:
- Around line 264-275: Reject any non-http(s) URL (except "about:blank") in
loadWorkspaceBrowserUrl by validating nextUrl before calling
instance.view.webContents.loadURL; allow nextUrl === "about:blank" or use the
existing isHttpUrl helper (or new URL parsing) to permit only http/https
schemes, and if validation fails do not call loadURL (set instance.url to
"about:blank" or leave unchanged and return early, optionally emit/log a
warning) so renderer-driven IPC cannot cause non-http(s) navigations.

In `@desktop/src/renderer/app/workspace/workspace-tool-host.tsx`:
- Around line 349-381: The closeDynamicTab callback currently voids the Promise
from window.workspaceBrowser.destroy, losing errors; update the call in
closeDynamicTab so destruction errors are handled and logged (e.g., replace the
void call with window.workspaceBrowser.destroy({ browserViewId:
tab.browserViewId }).catch(err => console.error('Failed to destroy browser
view', { browserViewId: tab.browserViewId, tabId: tab.id, err }))); keep the
rest of closeDynamicTab logic the same so UI state updates proceed even if
destruction fails.
- Around line 989-1071: The getState, navigate, goBack, goForward and reload
calls currently swallow errors in their .catch(() => {}) blocks; update each
catch to log the error (including context) instead of ignoring it — for example
replace the empty catches on window.workspaceBrowser.getState, .navigate,
.goBack, .goForward and .reload with processLogger.error or console.error calls
that include the operation name, tab.browserViewId (or tab.id) and the caught
error; keep behavior otherwise (still call
handleStateChange/setBrowserState/updateBrowserTab as before).

In `@desktop/src/renderer/app/workspace/workspace-tool-store.ts`:
- Around line 89-96: The catch block on the promise from
window.desktopWindow.getWorkspaceToolSurfaceState() is swallowing errors; update
the .catch to log the error before marking hydration complete so failures are
visible. Specifically, in the promise chain that calls
getWorkspaceToolSurfaceState() and then syncWorkspaceToolState(state), change
the .catch handler to accept an error parameter and log it (e.g., console.error
or the app logger) and then call useWorkspaceToolStore.setState({ hydrated: true
}) so visibility is preserved while keeping behavior the same.

In `@desktop/src/shared/workspace-browser.ts`:
- Around line 76-77: The exposed IPC method return types for destroy and detach
are too broad (Promise<unknown>); update the WorkspaceBrowser interface
signatures for destroy and detach to return Promise<{ ok: true }> instead so
they match the actual responses sent on WORKSPACE_BROWSER_DESTROY_CHANNEL and
WORKSPACE_BROWSER_DETACH_CHANNEL; modify the function/type declarations named
destroy and detach in workspace-browser.ts to use the narrowed return type.

---

Nitpick comments:
In `@desktop/src/main/features/workspace-browser/ipc.ts`:
- Around line 264-275: The loadWorkspaceBrowserUrl function calls
instance.view.webContents.loadURL(...) without handling promise rejections;
update both places where loadURL is called in loadWorkspaceBrowserUrl to attach
a .catch() that logs the error (e.g., console.error or the app logger) and
optionally triggers any recovery/diagnostic behavior (such as emitting an
IPC/event or calling instance/WorkspaceBrowserInstance recovery methods) so
navigation failures are observed and can be acted on.

In `@desktop/src/main/features/workspace-tools/service.ts`:
- Line 115: The fallback to treePathInput when computing treePath is unreachable
because absolutePathToTreePath only returns null for paths that escape the
workspace and resolveWorkspaceTreePath already throws for those cases; remove
the unreachable "?? treePathInput" fallback and assign treePath directly from
absolutePathToTreePath(root, absolutePath), or alternatively handle the
out-of-workspace case explicitly where resolveWorkspaceTreePath is called
(adjust the call sites of absolutePathToTreePath and resolveWorkspaceTreePath
rather than keeping the redundant fallback).

In `@desktop/src/renderer/app/router.tsx`:
- Around line 22-25: Remove the trivial wrapper WorkspaceToolWindowRoutePage and
inline WorkspaceToolWindowPage in the Route so the route matches the
simple-route pattern used elsewhere: replace the Route using
component={WorkspaceToolWindowRoutePage} with the same inline form used in other
routes (e.g., component={WorkspaceToolWindowPage} or
element={<WorkspaceToolWindowPage />} to match the file's routing API) and
delete the now-unused WorkspaceToolWindowRoutePage function/definition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 20b36e52-3d28-4166-b673-57c315baa5bf

📥 Commits

Reviewing files that changed from the base of the PR and between 52aa86e and cf74c0b.

📒 Files selected for processing (27)
  • desktop/src/main/features/terminal/ipc.ts
  • desktop/src/main/features/workspace-browser/ipc.ts
  • desktop/src/main/features/workspace-tools/ipc.ts
  • desktop/src/main/features/workspace-tools/service.ts
  • desktop/src/main/ipc/register.ts
  • desktop/src/main/windows/workspace-tool-window.ts
  • desktop/src/preload/bridges/desktop-window.ts
  • desktop/src/preload/bridges/terminal.ts
  • desktop/src/preload/bridges/workspace-browser.ts
  • desktop/src/preload/index.ts
  • desktop/src/renderer/app/router.tsx
  • desktop/src/renderer/app/workspace/workspace-browser-view.tsx
  • desktop/src/renderer/app/workspace/workspace-page.tsx
  • desktop/src/renderer/app/workspace/workspace-right-sidebar.tsx
  • desktop/src/renderer/app/workspace/workspace-terminal-view.tsx
  • desktop/src/renderer/app/workspace/workspace-tool-host.tsx
  • desktop/src/renderer/app/workspace/workspace-tool-store.ts
  • desktop/src/renderer/app/workspace/workspace-ui-store.ts
  • desktop/src/renderer/env.d.ts
  • desktop/src/renderer/platform/api-client.ts
  • desktop/src/renderer/platform/query-keys.ts
  • desktop/src/shared/desktop-window.ts
  • desktop/src/shared/terminal.ts
  • desktop/src/shared/workspace-browser.ts
  • desktop/src/shared/workspace-tool-instances.ts
  • desktop/src/shared/workspace-tool-surface.ts
  • desktop/src/shared/workspace-tools.ts
💤 Files with no reviewable changes (1)
  • desktop/src/renderer/app/workspace/workspace-ui-store.ts

Comment thread desktop/src/main/features/workspace-browser/ipc.ts Outdated
Comment thread desktop/src/renderer/app/workspace/workspace-tool-host.tsx
Comment thread desktop/src/renderer/app/workspace/workspace-tool-host.tsx Outdated
Comment thread desktop/src/renderer/app/workspace/workspace-tool-store.ts
Comment thread desktop/src/shared/workspace-browser.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

const buffer = await fs.readFile(absolutePath);

P1 Badge Reject symlinked untracked files

This is separate from preview reads: the new untracked-diff path follows untracked symlinks before generating the Git panel patch. If a workspace contains an untracked symlink like secret -> /etc/passwd, fs.stat() follows it and this readFile() includes the target contents in the displayed diff, even though Git would treat the symlink itself as a small file containing the link target. Use lstat/reject symlinks or realpath containment before reading untracked files.



P2 Badge Limit diffs to the selected workspace root

When the selected project is a subdirectory of a larger Git repo, this diff is run from gitRoot without a pathspec, while the status list above is explicitly filtered back to root. In that context the Git tools tab will show staged/unstaged changes from sibling directories outside the selected project, so users can review unrelated changes under the wrong workspace. Pass the root-relative path (after --) to both git diff calls.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +178 to +184
webContents.setWindowOpenHandler(({ url }) => {
if (isHttpUrl(url)) {
loadWorkspaceBrowserUrl(instance, url);
} else if (isAllowedExternalUrl(url)) {
void shell.openExternal(url);
}
return { action: "deny" };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Block non-HTTP main-frame navigations

This only handles window.open, so a loaded page can still drive the current workspace browser via a top-level redirect or location.href to a non-HTTP URL and bypass the workspaceBrowserLoadUrl() check used for user-entered URLs. In that scenario the WebContentsView attempts the navigation and later records the resulting URL through did-navigate, so add a will-navigate/redirect guard that enforces the same http(s)/about:blank policy for main-frame navigations.

Useful? React with 👍 / 👎.

@AkaraChen AkaraChen merged commit 9ef48d0 into master Jun 11, 2026
3 checks passed
@AkaraChen AkaraChen deleted the workspace-tool-surface-tabs branch June 11, 2026 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant